/**
 * Copyright (C) 2010 Cloudfarming <info@cloudfarming.nl>
 *
 * Licensed under the Eclipse Public License - v 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.cloudfarming.client.sensor.cropcircle;

import nl.cloudfarming.client.sensor.cropcircle.model.CropcircleSensorData;
import java.io.File;
import java.io.FileInputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.EnumMap;
import java.util.List;
import nl.cloudfarming.client.geoviewer.Layer;
import nl.cloudfarming.client.geoviewer.LayerProvider;
import nl.cloudfarming.client.logging.AppLogFactory;
import nl.cloudfarming.client.logging.AppLogger;
import nl.cloudfarming.client.model.SensorDataFile;
import nl.cloudfarming.client.model.SensorMetadata;
import nl.cloudfarming.client.sensor.SensorProcessor;
import nl.cloudfarming.client.sensor.cropcircle.Impl.CropcircleServiceImpl;
import nl.cloudfarming.client.util.MD5Sum;
import org.apache.commons.io.FileUtils;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;

/**
 *
 * @author Merijn Zengers
 */
public class CropcircleFileProcessor extends SensorProcessor<CropcircleSensorData> {

    private static final String LOGKEY_FAILED_TO_READ_FILE = "message.error.failed_to_read_file";
    private static final String LOGKEY_PROCESSING_SUMMARY_PROCESSEDMSG = "message.info.process_summary.processed";
    private static final String LOGKEY_PROCESSING_SUMMARY_ERRORMSG = "message.info.process_summary.errors";
    private static final String LOGKEY_PROCESSING_START = "message.info.process_start";
    
    private static final AppLogger LOG = AppLogFactory.getLogger(CropcircleFileProcessor.class);
    private CropcircleServiceImpl cropcircleService = new CropcircleServiceImpl();
    private static final String PROPERTY_MONTH = "MM";
    private static final String PROPERTY_YEAR = "YY";
    private static final String PROPERTY_YEAR_INTERNATIONAL = "YYYY";
    private static final String PROPERTY_DAY = "DD";
    private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HHmmss.SS");
    private final ProgressHandle progressHandle;

    public CropcircleFileProcessor(File toProcess) {
        super(toProcess);
        assert toProcess != null;
        this.progressHandle = ProgressHandleFactory.createHandle(NbBundle.getMessage(this.getClass(), "cropcircle.processor.processDescription", toProcess.getName()));
//                AggregateProgressFactory.createProgressContributor(NbBundle.getMessage(this.getClass(), "cropcircle.processor.processDescription", toProcess.getName()));
    }

    @Override
    public void process() {
        try {
            LOG.info(LOGKEY_PROCESSING_START, getProcessFile());

            SensorMetadata metadata = buildMetdataFromPreferences();
            Date runDate = makeDateFromFileName(getProcessFile().getName());
            String checkSum = MD5Sum.getHex(MD5Sum.createChecksum(new FileInputStream(getProcessFile())));
            String fileName = getProcessFile().getName();
            SensorDataFile dataFile = new SensorDataFile(fileName, checkSum, runDate, new Date(System.currentTimeMillis()), metadata);

            int success = 0;
            int fail = 0;
            int progress = 0;

            cropcircleService.persist(dataFile);

            List<CropcircleSensorData> persistBuffer = new ArrayList<CropcircleSensorData>();

            List<String> lines = (List<String>) FileUtils.readLines(getProcessFile(), "UTF-8");
            
            // Remove first line (Header)
            lines.remove(0);

            this.progressHandle.start(lines.size());
            for (String line : lines) {
                try {
                    CropcircleSensorData toPersist = parse(line, dataFile);
                    persistBuffer.add(toPersist);
                    success++;
                    if (success % 100 == 0) {
                        cropcircleService.persist(persistBuffer);
                        persistBuffer.clear();
                    }
                } catch (Exception ex) {
                    LOG.trace(ex);
                    fail++;
                }
                progressHandle.progress(++progress);
            }

            LOG.info(LOGKEY_PROCESSING_SUMMARY_PROCESSEDMSG, success);

            if(fail > 0){
                LOG.warning(LOGKEY_PROCESSING_SUMMARY_ERRORMSG, fail);
            }

            //Persist the dataFile. This wil result in persisting the sensordata and metadata as wel.
            cropcircleService.persist(persistBuffer);

            signalLayers();
        } catch (Exception ex) {
            LOG.error(LOGKEY_FAILED_TO_READ_FILE, getProcessFile().getName());
            LOG.trace(ex);
        }
        progressHandle.finish();
    }

    private void signalLayers() {
        //FIXME hack to update layers loading of sensor data needs refactoring to simpler architecture
        for (LayerProvider provider : Lookup.getDefault().lookupAll(LayerProvider.class)) {
            if (provider instanceof CropcircleDataLayerProvider) {
                for (Layer layer : ((CropcircleDataLayerProvider) provider).getLookup().lookupAll(Layer.class)) {
                    layer.updateLayer();
                }
            }
        }
    }

    protected CropcircleSensorData parse(String payload, SensorDataFile dataFile) throws ParseException {
        // First create a entity representation of a file
        //converting the differrent data
        EnumMap map = new EnumMap<CropcircleFileDescription, Object>(CropcircleFileDescription.class);
        String[] fields = payload.toString().split(CropcircleFileDescription.getDelimmiter());

        for (CropcircleFileDescription c : CropcircleFileDescription.values()) {
            if (c.isMandatory()) {
                if (c.getTheClass().equals(java.lang.Double.class)) {
                    double theDouble = Double.parseDouble(fields[c.getPosition()]);
                    map.put(c, theDouble);
                } else if (c.getTheClass().equals(java.lang.Integer.class)) {
                    int theInt = Integer.parseInt(fields[c.getPosition()]);
                    map.put(c, theInt);
                } else if (c.getTheClass().equals(java.util.Date.class)) {
                    Date theDate = TIME_FORMAT.parse(fields[c.getPosition()]);
                    map.put(c, theDate);
                } else {
                    map.put(c, fields[c.getPosition()]);
                }
            }
        }
        CropcircleSensorData sensorData = new CropcircleSensorData((SensorDataFile) dataFile, (Double) map.get(CropcircleFileDescription.LONGITUDE), (Double) map.get(CropcircleFileDescription.LATITUDE), (Integer) map.get(CropcircleFileDescription.FIX_TYPE), (Date) map.get(CropcircleFileDescription.UTC_TIME), (Double) map.get(CropcircleFileDescription.SPEED), (Double) map.get(CropcircleFileDescription.COURSE), (Double) map.get(CropcircleFileDescription.SENSOR), (Double) map.get(CropcircleFileDescription.VI1), (Double) map.get(CropcircleFileDescription.VI2), (Double) map.get(CropcircleFileDescription.R1), (Double) map.get(CropcircleFileDescription.R2), (Double) map.get(CropcircleFileDescription.R3), (String) map.get(CropcircleFileDescription.APP_RATE));

        return sensorData;
    }

    protected Date makeDateFromFileName(String fileName) throws MalformedFileFormatException, ParseException {
        String props = CropcirclePreference.SENSOR_DATA_FILE_FORMAT.getValue();
        assert !props.isEmpty();

        if (props.contains(PROPERTY_DAY) && props.contains(PROPERTY_MONTH) && props.contains(PROPERTY_YEAR)) {
            int numberOfYearCharacters = 2;
            if (props.contains(PROPERTY_YEAR_INTERNATIONAL)) {
                numberOfYearCharacters = 4;
            }
            String day = fileName.substring(props.indexOf(PROPERTY_DAY), props.indexOf(PROPERTY_DAY) + 2);
            String month = fileName.substring(props.indexOf(PROPERTY_MONTH), props.indexOf(PROPERTY_MONTH) + 2);
            String year = fileName.substring(props.indexOf(PROPERTY_YEAR), props.indexOf(PROPERTY_YEAR) + numberOfYearCharacters);
            StringBuilder dateBuilder = new StringBuilder(day);
            dateBuilder.append(month);
            dateBuilder.append(year);

            SimpleDateFormat date = new SimpleDateFormat("ddMMyy");
            return date.parse(dateBuilder.toString());
        } else {
            StringBuilder sb = new StringBuilder("The property which specifies file format is malformed while creating date from filename: ");
            sb.append(fileName);
            throw new MalformedFileFormatException(sb.toString());
        }
    }

    protected SensorMetadata buildMetdataFromPreferences() {
        String fileFormat = CropcirclePreference.SENSOR_DATA_FILE_FORMAT.getValue();
        int sensorHeigth = Integer.parseInt(CropcirclePreference.CROPCIRCLE_SENSOR_HEIGTH.getValue());
        int numberOfSensors = Integer.parseInt(CropcirclePreference.NUMBER_OF_CROPCIRCLE_SENSORS.getValue());
        String cropCircleSensorSpread = CropcirclePreference.CROPCIRCLE_SENSOR_SPREAD.getValue();
        String sensorType = CropcirclePreference.SENSOR_TYPE.getValue();
        String spectralChannels = CropcirclePreference.SPECTRAL_CHANNELS.getValue();
        return new SensorMetadata(fileFormat, sensorHeigth, numberOfSensors, cropCircleSensorSpread, sensorType, spectralChannels);
    }

}
